use std::ffi::{CStr, CString};

mod load;
mod sys;

use load::*;
use sys::*;

use crate::{Fallback, Stretch, Style, Weight, XFontType};

pub struct XFont {
	lib: *mut core::ffi::c_void,
	config: *mut FcConfig,
	fc_funs: FcFuns,
}

impl Default for XFont {
	fn default() -> Self {
		unsafe {
			let lib = dlopen(b"libfontconfig.so.1\0".as_ptr() as _, RTLD_LAZY);
			if lib.is_null() {
				println!(
					"fontconfig: failed to load libfontconfig.so.1, dlerror: {}",
					CStr::from_ptr(dlerror()).to_str().unwrap_or_default()
				);
				Self {
					lib: core::ptr::null_mut(),
					config: core::ptr::null_mut(),
					fc_funs: Default::default(),
				}
			} else {
				let fc_funs = FcFuns::load(lib);
				let config = if let Some(fc_init) = fc_funs.fc_init {
					fc_init()
				} else {
					core::ptr::null_mut()
				};
				Self { lib, config, fc_funs }
			}
		}
	}
}

impl Drop for XFont {
	fn drop(&mut self) {
		if let Some(fc_config_destroy) = self.fc_funs.fc_config_destroy {
			unsafe { fc_config_destroy(self.config) };
		}
		unsafe { dlclose(self.lib) };
	}
}

impl XFontType for XFont {
	fn fallback(&self, text: &str, name: &str, weight: Weight, stretch: Stretch, style: Style) -> Fallback {
		self.fallback_impl(text, name, weight, stretch, style).unwrap_or_default()
	}
	fn system() -> String {
		"sans-serif".to_string()
	}
}
impl XFont {
	fn fallback_impl(&self, text: &str, family_name: &str, weight: Weight, stretch: Stretch, style: Style) -> Option<Fallback> {
		if self.config.is_null() {
			println!("fontconfig: config is null");
			return None;
		}
		let text = if text.is_empty() { "a" } else { text };
		let mut fallback = None;
		unsafe {
			let pattern_create = self.fc_funs.fc_pattern_create?;
			let pattern_destroy = self.fc_funs.fc_pattern_destroy?;
			let pattern_add_string = self.fc_funs.fc_pattern_add_string?;
			let pattern_add_integer = self.fc_funs.fc_pattern_add_integer?;
			let pattern_add_charset = self.fc_funs.fc_pattern_add_charset?;
			let pattern_get_string = self.fc_funs.fc_pattern_get_string?;
			let pattern_get_integer = self.fc_funs.fc_pattern_get_integer?;
			let char_set_create = self.fc_funs.fc_char_set_create?;
			let char_set_destroy = self.fc_funs.fc_char_set_destroy?;
			let char_set_add_char = self.fc_funs.fc_char_set_add_char?;
			let config_substitute = self.fc_funs.fc_config_substitute?;
			let default_substitute = self.fc_funs.fc_default_substitute?;
			let font_match = self.fc_funs.fc_font_match?;

			let pattern = pattern_create();
			if !pattern.is_null() {
				pattern_add_string(pattern, FC_FAMILY.as_ptr(), CString::new(family_name).unwrap().as_ptr() as _);
				pattern_add_integer(pattern, FC_WEIGHT.as_ptr(), conv_weight(weight));
				pattern_add_integer(pattern, FC_WIDTH.as_ptr(), conv_stretch(stretch));
				pattern_add_integer(pattern, FC_SLANT.as_ptr(), conv_style(style));
				let char_set = char_set_create();
				if !char_set.is_null() {
					for c in text.chars() {
						char_set_add_char(char_set, c as _);
					}
					pattern_add_charset(pattern, FC_CHARSET.as_ptr(), char_set);

					config_substitute(self.config, pattern, FcMatchPattern);
					default_substitute(pattern);

					let mut result = FcResultNoMatch;
					let matched = font_match(self.config, pattern, &mut result);
					if !matched.is_null() {
						let mut file = core::ptr::null_mut();
						pattern_get_string(matched, FC_FILE.as_ptr(), 0, &mut file);
						let mut index = 0;
						pattern_get_integer(matched, FC_INDEX.as_ptr(), 0, &mut index);
						if !file.is_null() {
							if let Ok(file) = CStr::from_ptr(file as _).to_str() {
								fallback = Some(Fallback {
									file: file.into(),
									index: Some(index as _),
									unique: None,
								})
							}
						}
						pattern_destroy(matched);
					}
					char_set_destroy(char_set);
				}
				pattern_destroy(pattern);
			}
		}
		fallback
	}
}

fn conv_weight(weight: Weight) -> i32 {
	match weight.0 {
		v if v == Weight::THIN.0 => FC_WEIGHT_THIN,
		v if v == Weight::EXTRA_LIGHT.0 => FC_WEIGHT_EXTRALIGHT,
		v if v == Weight::LIGHT.0 => FC_WEIGHT_LIGHT,
		v if v == Weight::NORMAL.0 => FC_WEIGHT_NORMAL,
		v if v == Weight::MEDIUM.0 => FC_WEIGHT_MEDIUM,
		v if v == Weight::SEMIBOLD.0 => FC_WEIGHT_SEMIBOLD,
		v if v == Weight::BOLD.0 => FC_WEIGHT_BOLD,
		v if v == Weight::EXTRA_BOLD.0 => FC_WEIGHT_EXTRABOLD,
		v if v == Weight::BLACK.0 => FC_WEIGHT_BLACK,
		_ => FC_WEIGHT_NORMAL,
	}
}
fn conv_stretch(stretch: Stretch) -> i32 {
	match stretch.0 {
		v if v == Stretch::ULTRA_CONDENSED.0 => FC_WIDTH_ULTRACONDENSED,
		v if v == Stretch::EXTRA_CONDENSED.0 => FC_WIDTH_EXTRACONDENSED,
		v if v == Stretch::CONDENSED.0 => FC_WIDTH_CONDENSED,
		v if v == Stretch::SEMI_CONDENSED.0 => FC_WIDTH_SEMICONDENSED,
		v if v == Stretch::NORMAL.0 => FC_WIDTH_NORMAL,
		v if v == Stretch::SEMI_EXPANDED.0 => FC_WIDTH_SEMIEXPANDED,
		v if v == Stretch::EXPANDED.0 => FC_WIDTH_EXPANDED,
		v if v == Stretch::EXTRA_EXPANDED.0 => FC_WIDTH_EXTRAEXPANDED,
		v if v == Stretch::ULTRA_EXPANDED.0 => FC_WIDTH_ULTRAEXPANDED,
		_ => FC_WIDTH_NORMAL,
	}
}
fn conv_style(style: Style) -> i32 {
	match style {
		Style::Normal => FC_SLANT_ROMAN,
		Style::Italic => FC_SLANT_ITALIC,
		Style::Oblique => FC_SLANT_OBLIQUE,
	}
}
